package org.apache.solr.ocean.http;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.lang.StringUtils;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.ocean.util.Timeout;
import org.apache.solr.ocean.util.Util;
import org.apache.solr.ocean.util.Timeout.TimeoutException;


/**
 *
 * @author jasonr
 */
public class CHttpClient {
  public static Logger log = Logger.getLogger(CHttpClient.class.getName());
  InetAddress thisAddress;

  public CHttpClient() {
  }

  public CHttpClient(InetAddress thisAddress) {
    this.thisAddress = thisAddress;
  }
  
  public static class StreamResponse {
  	HttpMethod httpMethod;
  	
  	public StreamResponse(HttpMethod httpMethod) {
  		this.httpMethod = httpMethod;
  	}
  	
  	public InputStream getInputStream() throws Exception {
  		return httpMethod.getResponseBodyAsStream();
  	}
  	
  	public void close() {
  		httpMethod.releaseConnection();
  	}
  }
  
  public static StreamResponse getStreamResponse(HttpRequest httpRequest) throws Exception {
  	HttpClient httpClient = new HttpClient();
  	HttpMethod httpMethod = createMethod(httpRequest);
  	httpClient.executeMethod(httpMethod);
  	return new StreamResponse(httpMethod);
  }
  
  public static HttpResponse request(HttpRequest httpRequest) throws TimeoutException, IOException {
    CHttpClient httpClient = new CHttpClient();
    return httpClient.doRequest(httpRequest);
  }

  public HttpResponse doRequest(HttpRequest httpRequest) throws TimeoutException, IOException {
    DoRequest doRequest = new DoRequest(httpRequest);
    return doRequest.doRequest();
  }
  
  public class DoRequest {
    HttpRequest httpRequest;
    HttpMethod httpMethod;

    public DoRequest(HttpRequest httpRequest) {
      this.httpRequest = httpRequest;
    }
    public class TimeoutHandler extends Timeout.Listener {
      public void cancelled() {
        if (httpMethod != null) {
          httpMethod.abort();
        }
      }
    }

    public HttpResponse doRequest() throws IOException, TimeoutException {
      //log.info("http " + httpRequest);
      int retries = 1;
      if (httpRequest.httpTimeout != null) {
      	// TODO: change back when makes sense
        //retries = httpRequest.timeout.retries;
      }
      for (int x = 1; x <= retries; x++) {
        if (httpRequest.httpTimeout != null && httpRequest.httpTimeout.timeout.notOK()) {
          throw new TimeoutException(httpRequest.httpTimeout.timeout);
        }
        try {
          httpMethod = createMethod(httpRequest);
          HttpClient httpClient = new HttpClient();
          if (thisAddress != null) {
            httpClient.getHostConfiguration().setLocalAddress(thisAddress);
          }
          if (httpRequest.httpTimeout != null) {
            httpClient.getParams().setSoTimeout(httpRequest.httpTimeout.socketTimeout);
            httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(httpRequest.httpTimeout.httpTimeout);
          }
          httpClient.executeMethod(httpMethod);
          int statusCode = httpMethod.getStatusCode();
          if (statusCode == 200) {
            return new HttpResponse(httpMethod);
          } else {
            String response = httpMethod.getResponseBodyAsString();
            throw new Exception("status code: "+statusCode+" "+response);
          }
        } catch (Exception exception) {
          if (x == retries) {
          	log.log(Level.SEVERE, exception.getMessage());
            Util.throwAsIOException("max retries reached: " + x + " " + httpRequest, exception);
          }
        } finally {
          if (httpMethod != null) {
            httpMethod.releaseConnection();
          }
        }
      }
      throw new RuntimeException("failed");
    }
  }

  public static NameValuePair[] toNameValuePairs(NamedList<String> parameters) { 
    NameValuePair[] nameValuePairs = new NameValuePair[parameters.size()];
    int count = 0;
    
    for (Map.Entry<String, String> entry : parameters) {
      if (StringUtils.isBlank(entry.getKey()) || StringUtils.isBlank(entry.getValue())) {
        continue;
      }
      nameValuePairs[count] = new NameValuePair(entry.getKey(), entry.getValue());
      count++;
    }
    return nameValuePairs;
  }

  protected static HttpMethod createMethod(HttpRequest httpRequest) throws IOException {
    if (StringUtils.equals("get", httpRequest.method)) {
      GetMethod getMethod = new GetMethod(httpRequest.url.toString());
      if (httpRequest.parameters != null) {
        NameValuePair[] parameters = toNameValuePairs(httpRequest.parameters);
        getMethod.setQueryString(parameters);
      }
      return getMethod;
    } else {
      if (StringUtils.equalsIgnoreCase("post", httpRequest.method)) {
        PostMethod postMethod = new PostMethod(httpRequest.url.toString()+"/");
        if (httpRequest.body != null && httpRequest.bodyContentType != null) {
          postMethod.setRequestEntity(new ByteArrayRequestEntity(httpRequest.body, httpRequest.bodyContentType));
        }
        return postMethod;
      } else {
        throw new IllegalArgumentException("httpRequest.method: " + httpRequest.method + " not supported");
      }
    }
  }
}
